﻿namespace WebPacker.Tests.Providers
{
	using System;
	using System.Collections.Specialized;
	using System.IO;
	using System.Web;
	using Moq;
	using NUnit.Framework;
	using WebPacker.Compressors;
	using WebPacker.Helpers;
	using WebPacker.Packs;
	using WebPacker.Packs.Resources;
	using WebPacker.Providers;
	using WebPacker.VersionGenerators;

	[TestFixture]
	public class MemoryPackProviderTest
	{
		[Test]
		public void GetUrlTest()
		{
			var provider = new MemoryPackProvider()
			{
				Url = "~/webpacker.axd"
			};

			VirtualPathHelper.ApplicationVirtualPath = "/app";
			var pack = GetPack(PackType.JavaScript, "script", "123");
			Assert.AreEqual("/app/webpacker.axd?script.js.gzip", provider.GetUrl(new TestCompressor("gzip"), pack));
		}

		[Test]
		public void GetUrl2Test()
		{
			var provider = new MemoryPackProvider()
			{
				Url = "~/resources"
			};

			VirtualPathHelper.ApplicationVirtualPath = "/app";
			var pack = GetPack(PackType.Css, "style", "abc");
			Assert.AreEqual("/app/resources?style.css", provider.GetUrl(new TestCompressor(string.Empty), pack));
		}

		[Test]
		public void ProcessTest()
		{
			VirtualPathHelper.ApplicationVirtualPath = "/app";

			var provider = new MemoryPackProvider
			{
				Url = "~/webpacker.axd"
			};

			var pack = GetPack(PackType.JavaScript, "test", "123");

			var url = provider.GetUrl(new NullCompressor(), pack);

			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;

			Mock.Get(context.Request).Setup(x => x.Url).Returns(new Uri("http://example.org" + url));

			provider.Process(context);

			Assert.AreEqual(0, context.Response.StatusCode);
		}

		[Test]
		public void ProcessDirectTest(
			[Values(true, false)]
			bool setEtag)
		{
			var provider = new MemoryPackProvider();

			var pack = GetPack(PackType.JavaScript, "test", "123");
			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;
			var response = context.Response;
			var request = context.Request;

			if (setEtag)
			{
				request.Headers.Add("If-None-Match", pack.Version + "x"); // invalid etag
			}

			provider.ProcessDirect(context, new NullCompressor(), pack);

			Assert.AreEqual(0, response.StatusCode);

			Assert.AreEqual("application/x-javascript", response.ContentType);
			Assert.AreEqual("utf-8", response.Charset);

			Assert.IsNotNull(response.Headers);
			Assert.AreEqual(4, response.Headers.Count);
			Assert.AreEqual("12", response.Headers["Content-Length"]);
			Assert.AreEqual("public, must-revalidate", response.Headers["Cache-Control"]);
			Assert.AreEqual("123", response.Headers["ETag"]);
			Assert.AreEqual("Accept-Encoding", response.Headers["Vary"]);

			Assert.IsNotNull(response.OutputStream);
			response.OutputStream.Seek(0, SeekOrigin.Begin);
			Assert.AreEqual(pack.GetContent(), new StreamReader(response.OutputStream).ReadToEnd());
		}

		[Test]
		public void ProcessDirectWithGzipTest()
		{
			var provider = new MemoryPackProvider();
			var pack = GetPack(PackType.JavaScript, "test", "123");
			var compressor = new GZipCompressor();
			var outputStream = new MemoryStream();
			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;
			var response = context.Response;

			compressor.Compress(pack.GetContent(), outputStream);
			outputStream.Seek(0, SeekOrigin.Begin);

			provider.ProcessDirect(context, compressor, pack);

			Assert.AreEqual(0, response.StatusCode);

			Assert.AreEqual("application/x-javascript", response.ContentType);
			Assert.AreEqual("utf-8", response.Charset);

			Assert.IsNotNull(response.Headers);
			Assert.AreEqual(5, response.Headers.Count);
			Assert.AreEqual("134", response.Headers["Content-Length"]);
			Assert.AreEqual("gzip", response.Headers["Content-Encoding"]);
			Assert.AreEqual("public, must-revalidate", response.Headers["Cache-Control"]);
			Assert.AreEqual("123", response.Headers["ETag"]);
			Assert.AreEqual("Accept-Encoding", response.Headers["Vary"]);

			Assert.IsNotNull(response.OutputStream);
			response.OutputStream.Seek(0, SeekOrigin.Begin);
			Assert.AreEqual(outputStream.GetBuffer(), ((MemoryStream)response.OutputStream).GetBuffer());
		}

		[Test]
		public void ProcessDirectWithCacheDurationTest(
			[Values(true, false)]
			bool setLastModified)
		{
			var duration = 60;

			var provider = new MemoryPackProvider
			{
				CacheDuration = duration
			};

			var pack = GetPack(PackType.Css, "test", "123");
			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;
			var response = context.Response;
			var request = context.Request;

			if (setLastModified)
			{
				request.Headers.Add("Last-Modified", DateTime.Now.AddDays(1).ToUniversalTime().ToString("R"));
			}

			provider.ProcessDirect(context, new NullCompressor(), pack);
			var now = DateTime.Now;

			Assert.AreEqual(0, response.StatusCode);

			Assert.AreEqual("text/css", response.ContentType);
			Assert.AreEqual("utf-8", response.Charset); 
			
			Assert.IsNotNull(response.Headers);
			Assert.AreEqual(5, response.Headers.Count);
			Assert.AreEqual("12", response.Headers["Content-Length"]);
			Assert.AreEqual(now.AddSeconds(duration).ToString("R"), response.Headers["Expires"]);
			Assert.AreEqual("public, must-revalidate, max-age=" + duration.ToString(), response.Headers["Cache-Control"]);
			Assert.AreEqual("123", response.Headers["ETag"]);
			Assert.AreEqual("Accept-Encoding", response.Headers["Vary"]);

			Assert.IsNotNull(response.OutputStream);
			response.OutputStream.Seek(0, SeekOrigin.Begin);
			Assert.AreEqual(pack.GetContent(), new StreamReader(response.OutputStream).ReadToEnd());
		}

		[Test]
		public void ProcessDirectWithLastModifiedTest()
		{
			var provider = new MemoryPackProvider
			{
				SetLastModified = true
			};
			var pack = GetPack(PackType.Css, "pack", "123");
			var compressor = new NullCompressor();
			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;

			provider.ProcessDirect(context, compressor, pack);

			Assert.IsNotNull(context.Response.Headers);
			Assert.AreEqual(5, context.Response.Headers.Count);
			Assert.AreEqual(pack.GetLastModifiedTime().ToUniversalTime().ToString("R"), context.Response.Headers["Last-Modified"]);
		}

		[Test]
		public void ProcessDirectIfNoneMatchTest()
		{
			var provider = new MemoryPackProvider();
			var pack = GetPack(PackType.JavaScript, "test", "123");
			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;
			var response = context.Response;

			context.Request.Headers.Add("If-None-Match", pack.Version);

			provider.ProcessDirect(context, new NullCompressor(), pack);

			Assert.AreEqual(304, response.StatusCode);

			Assert.IsNotNull(response.Headers);
			Assert.AreEqual(4, response.Headers.Count);
			Assert.AreEqual("0", response.Headers["Content-Length"]);
			Assert.AreEqual("public, must-revalidate", response.Headers["Cache-Control"]);
			Assert.AreEqual("123", response.Headers["ETag"]);
			Assert.AreEqual("Accept-Encoding", response.Headers["Vary"]);

			Assert.IsNotNull(response.OutputStream);
			Assert.AreEqual(0, response.OutputStream.Length);
		}

		[Test]
		public void ProcessDirectIfModifiedSinceTest()
		{
			var provider = new MemoryPackProvider
			{
				SetLastModified = true
			};

			var pack = GetPack(PackType.JavaScript, "test", "123");
			var contextMock = GetHttpContextMock();
			var context = contextMock.Object;
			var response = context.Response;
			var lastModified = pack.GetLastModifiedTime().ToUniversalTime().ToString("R");

			context.Request.Headers.Add("If-Modified-Since", lastModified);

			provider.ProcessDirect(context, new NullCompressor(), pack);

			Assert.AreEqual(304, response.StatusCode);

			Assert.IsNotNull(response.Headers);
			Assert.AreEqual(5, response.Headers.Count);
			Assert.AreEqual("0", response.Headers["Content-Length"]);
			Assert.AreEqual("public, must-revalidate", response.Headers["Cache-Control"]);
			Assert.AreEqual("123", response.Headers["ETag"]);
			Assert.AreEqual("Accept-Encoding", response.Headers["Vary"]);
			Assert.AreEqual(lastModified, response.Headers["Last-Modified"]);

			Assert.IsNotNull(response.OutputStream);
			Assert.AreEqual(0, response.OutputStream.Length);
		}

		private Mock<HttpContextBase> GetHttpContextMock()
		{
			var contextMock = new Mock<HttpContextBase>();
			var requestMock = new Mock<HttpRequestBase>();
			var responseMock = new Mock<HttpResponseBase>();
			var requestHeaders = new NameValueCollection();
			var responseHeaders = new NameValueCollection();
			var outputStream = new MemoryStream();

			contextMock.Setup(x => x.Request).Returns(requestMock.Object);
			contextMock.Setup(x => x.Response).Returns(responseMock.Object);

			requestMock.Setup(x => x.HttpMethod).Returns("GET");
			requestMock.Setup(x => x.Headers).Returns(requestHeaders);

			responseMock.Setup(x => x.Headers).Returns(responseHeaders);
			responseMock.Setup(x => x.AppendHeader(It.IsAny<string>(), It.IsAny<string>()))
				.Callback<string, string>((header, value) => responseHeaders.Add(header, value));
			responseMock.SetupAllProperties();

			responseMock.Setup(x => x.OutputStream).Returns(outputStream);

			return contextMock;
		}

		private Pack GetPack(PackType packType, string packName, string version)
		{
			var resources = new IResource[] { new ContentResource("test", "hello world!") };
			return new Pack(packType, packName, resources, new TestFilter(packType), null, new ManualVersionGenerator(), version);
		}
	}
}
